<?php

namespace App\Console\Commands\Bank;

use App\Models\BankDetailHangzhou;
use App\Services\Bank\BankServiceFactory;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

class RetrieveHangzhouReceiptsCommand extends Command
{
    protected $signature = 'bank:hangzhou-receipts
        {--date= : 指定交易日期（格式：Y-m-d），默认取昨日交易}
        {--force : 忽略两小时限制强制重新请求}';

    protected $description = '杭州银行交易回单自动取回';

    private const BANK_DRIVER = 'BankOfHangzhou';
    private const CACHE_PREFIX = 'bank:hangzhou:receipt:';
    private const CACHE_TTL_MINUTES = 120;
    private const RECEIPT_DIRECTORY = 'receipts';

    public function handle(): int
    {
        $targetDate = $this->resolveTargetDate();
        if (! $targetDate) {
            return self::FAILURE;
        }

        $storageDisk = config('bank.receipts_disk', config('filesystems.default', 'local'));
        $storage = Storage::disk($storageDisk);
        $this->info(sprintf('使用存储盘 "%s"，目标日期：%s', $storageDisk, $targetDate->toDateString()));

        try {
            $bankService = BankServiceFactory::make(self::BANK_DRIVER);
        } catch (\Throwable $exception) {
            $this->error('银行服务初始化失败：' . $exception->getMessage());
            Log::error('[ReceiptRetrieve] 初始化银行服务失败', [
                'bank' => self::BANK_DRIVER,
                'error' => $exception->getMessage(),
            ]);
            return self::FAILURE;
        }

        $query = BankDetailHangzhou::query()
            ->whereDate('trans_date', $targetDate->toDateString())
            ->whereNull('receipt_path');

        $force = (bool) $this->option('force');
        $total = 0;
        $skipped = 0;
        $success = 0;
        $failed = 0;

        $query->chunkById(100, function ($records) use (
            $bankService,
            $storage,
            $storageDisk,
            $force,
            &$total,
            &$skipped,
            &$success,
            &$failed
        ) {
            foreach ($records as $record) {
                /** @var BankDetailHangzhou $record */
                $total++;
                $serialNo = $record->serial_no;
                $cacheKey = self::CACHE_PREFIX . $serialNo;

                if (! $force && ! Cache::add($cacheKey, now()->toDateTimeString(), now()->addMinutes(self::CACHE_TTL_MINUTES))) {
                    $skipped++;
                    $this->line(sprintf('<comment>跳过 %s：两小时内已请求过</comment>', $serialNo));
                    continue;
                }

                if ($force) {
                    Cache::put($cacheKey, now()->toDateTimeString(), now()->addMinutes(self::CACHE_TTL_MINUTES));
                }

                $result = $bankService->downloadSingleReceipt($serialNo);

                if (! ($result['success'] ?? false)) {
                    $failed++;
                    $this->warn(sprintf('回单获取失败 [%s]：%s', $serialNo, $result['ret_msg'] ?? '银行接口调用失败'));
                    Log::warning('[ReceiptRetrieve] 回单获取失败', [
                        'serial_no' => $serialNo,
                        'ret_code' => $result['ret_code'] ?? null,
                        'ret_msg' => $result['ret_msg'] ?? null,
                    ]);
                    continue;
                }

                $receiptContent = $result['map_data']['elecReceipt'] ?? null;
                if (! $receiptContent) {
                    $failed++;
                    $this->warn(sprintf('回单数据为空 [%s]', $serialNo));
                    Log::warning('[ReceiptRetrieve] 回单数据为空', [
                        'serial_no' => $serialNo,
                        'map_data' => $result['map_data'] ?? [],
                    ]);
                    continue;
                }

                $binaryContent = base64_decode($receiptContent, true);
                if ($binaryContent === false) {
                    $failed++;
                    $this->error(sprintf('回单base64解码失败 [%s]', $serialNo));
                    Log::error('[ReceiptRetrieve] 回单解码失败', [
                        'serial_no' => $serialNo,
                    ]);
                    continue;
                }

                $path = sprintf(
                    '%s/%s/%s.pdf',
                    self::RECEIPT_DIRECTORY,
                    $record->trans_date?->format('Ymd') ?? $record->trans_date,
                    $serialNo
                );

                try {
                    $storage->put($path, $binaryContent, ['visibility' => 'private']);
                } catch (\Throwable $exception) {
                    $failed++;
                    $this->error(sprintf('回单写入失败 [%s]：%s', $serialNo, $exception->getMessage()));
                    Log::error('[ReceiptRetrieve] 回单写入失败', [
                        'serial_no' => $serialNo,
                        'disk' => $storageDisk,
                        'error' => $exception->getMessage(),
                    ]);
                    continue;
                }

                $record->receipt_path = $path;
                $record->receipt_retrieved_at = now();
                $record->save();

                $success++;
                $this->info(sprintf('回单取回成功 [%s] -> %s', $serialNo, $path));
            }
        });

        $this->line('');
        $this->info(sprintf(
            '处理完成：总数 %d，成功 %d，跳过 %d，失败 %d。',
            $total,
            $success,
            $skipped,
            $failed
        ));

        return self::SUCCESS;
    }

    private function resolveTargetDate(): ?Carbon
    {
        $dateOption = $this->option('date');

        if (! $dateOption) {
            return Carbon::yesterday()->startOfDay();
        }

        try {
            return Carbon::parse($dateOption)->startOfDay();
        } catch (\Throwable $exception) {
            $this->error('日期格式不正确，请使用 Y-m-d，例如 2025-10-22');
            return null;
        }
    }
}
